/**
 * @file RandomProvider.h
 * @brief Header file for the RandomProvider class, a versatile tool for generating random values.
 *
 * The RandomProvider class is a singleton designed to facilitate the generation of random values across various types
 * including integers, floating points, booleans, and strings. It leverages the C++ <random> library to offer a robust
 * solution for random number generation. The class provides a thread-safe mechanism for random value generation, making
 * it suitable for multi-threaded applications. It features support for custom ranges, allowing users to specify the bounds
 * within which random values should be generated.
 *
 * Additionally, the class supports C++17 features such as std::optional and std::variant, enhancing its functionality
 * and making it adaptable to modern C++ projects. It provides efficient and flexible random generation methods, catering
 * to a wide range of use cases from testing to simulation.
 *
 * Specializations for generating random strings and handling boolean probabilities are included, demonstrating the class's
 * ability to handle non-numeric random generation tasks. The RandomProvider class is crucial for developers requiring
 * reliable, thread-safe, and versatile random generation capabilities in their C++ applications.
 *
 * Example usage includes generating random numbers within a specified range, creating random strings for testing purposes,
 * and simulating probabilistic scenarios. The class ensures high-quality randomness and reproducibility, important for
 * applications that rely on random data generation.
 *
 * @note To maintain singleton behavior and ensure thread safety, the RandomProvider class is not copyable or movable.
 * 
 * @author liuzhiyu (liuzhiyu27@foxmail.com)
 * @copyright Copyright (c) 2023 by liuzhiyu, All Rights Reserved.
 * 
 */
#pragma once
#include <iostream>
#include <random>
#include <map>
#include <functional>
#include <type_traits>
#include <chrono>
#include <mutex>
#include "DefaultInternalModuleFactory.hpp"
#include "SoftWareVersion.h"
/**
 * @brief A template structure to define the range for random value generation.
 * 
 * Range is used to specify the minimum and maximum values for generating random numbers.
 * It has specializations for different types, including numeric types, std::string, and bool.
 *
 * @tparam T The type of the range.
 */
template <typename T>
struct Range {
    T min_val; ///< The minimum value in the range.
    T max_val; ///< The maximum value in the range.

#ifdef USE_CPP17_FEATURES
    /**
     * @brief Default constructor that initializes the range to the type's limits.
     */
    Range() : min_val(default_min()), max_val(default_max()) {}

    /**
     * @brief Constructor to set specific minimum and maximum values.
     * @param min The minimum value.
     * @param max The maximum value.
     */
    Range(const T& min, const T& max) : min_val(min), max_val(max) {}

    /**
     * @brief Retrieves the default minimum value for the type.
     * @return The default minimum value.
     * @throws std::runtime_error if the type cannot be default constructed.
     */
    static T default_min() {
        if constexpr (std::numeric_limits<T>::is_specialized) {
            return std::numeric_limits<T>::min();
        } else if constexpr (std::is_default_constructible_v<T>) {
            return T();
        } else {
            throw std::runtime_error("Type cannot be default constructed");
        }
    }
    /**
     * @brief Retrieves the default maximum value for the type.
     * @return The default maximum value.
     * @throws std::runtime_error if the type cannot be default constructed.
     */
    static T default_max() {
        if constexpr (std::numeric_limits<T>::is_specialized) {
            return std::numeric_limits<T>::max();
        } else if constexpr (std::is_default_constructible_v<T>) {
            return T();
        } else {
            throw std::runtime_error("Type cannot be default constructed");
        }
    }
#else
    double probability_of_true; ///< For bool, the probability of being true.
    size_t min_len; ///< For std::string, the minimum length.
    size_t max_len; ///< For std::string, the maximum length.
#endif
};
// Specialization of Range for std::string
template <>
struct Range<std::string> {
    int min_len; ///< The minimum length of the string.
    int max_len; ///< The maximum length of the string.

    /**
     * @brief Constructor for Range with string-specific parameters.
     * @param min The minimum string length.
     * @param max The maximum string length.
     */
    Range(int min = 1, int max = 10) : min_len(min), max_len(max) {}
};
// Specialization of Range for bool
template <>
struct Range<bool> {
    double probability_of_true;  ///< The probability of the bool being true.
    double probability_of_false; ///< The probability of the bool being false.

    /**
     * @brief Constructor for Range with boolean-specific probability.
     * @param prob The probability of the bool being true.
     */
    Range(double prob = 0.5) : Range(prob, 1.0 - prob) {}

    /**
     * @brief Constructor for Range with specific probabilities for true and false.
     * @param prob_true Probability of being true.
     * @param prob_false Probability of being false.
     */
    Range(double prob_true, double prob_false) 
        : probability_of_true(prob_true), probability_of_false(prob_false) {
        if (probability_of_true < 0.0 || probability_of_true > 1.0 ||
            probability_of_false < 0.0 || probability_of_false > 1.0 ||
            probability_of_true + probability_of_false != 1.0) {
                probability_of_true = 0.5;
                probability_of_false = 0.5;
        }
    }
};
/**
 * @brief Generates a seed for the random number generator.
 * 
 * This function generates a seed using std::random_device, and falls back to time-based seeding
 * if std::random_device does not provide sufficient entropy.
 * 
 * @return A seed value for random number generation.
 */
inline unsigned int getSeed() {
    std::random_device rd;
    if (rd.entropy() != 0) {
        return rd();
    } else {
        return static_cast<unsigned int>(std::chrono::steady_clock::now().time_since_epoch().count());
    }
}
/**
 * @class RandomProvider
 * @brief A versatile and thread-safe class for generating random values.
 *
 * RandomProvider is a singleton class designed to offer a convenient and consistent 
 * way of generating random values of various types, including integers, floating points, 
 * booleans, and strings. It leverages the modern C++ <random> library to provide a 
 * robust and flexible approach to random number generation.
 *
 * The class uses `std::mt19937` (Mersenne Twister) as the underlying pseudorandom 
 * number generator, ensuring high-quality randomness and reproducibility when needed.
 *
 * Key features:
 * - Generate random values for fundamental data types and strings.
 * - Support for setting custom ranges for generated values.
 * - Thread-safe random number generation, suitable for multi-threaded applications.
 * - Option to use different seeds for different instances or threads.
 * - Efficient handling of random generation for both integral and floating-point types.
 *
 * Example Usage:
 *     RandomProvider& rnd = RandomProvider::getInstance();
 *     int randomInt = rnd.getDefaultRandom<int>(Range<int>(1, 10));
 *     double randomDouble = rnd.getDefaultRandom<double>(Range<double>(0.0, 1.0));
 *     std::string randomString = rnd.getDefaultRandom<std::string>(Range<std::string>(5, 10));
 *
 * @note The RandomProvider class is not copyable or movable to ensure singleton behavior.
 *       It provides thread-local storage for the default random generator to ensure thread safety.
 */
class __attribute__((visibility("default"))) RandomProvider {
    friend class DefaultInternalModuleFactory;
private:
    /**
     * @brief Private constructor for RandomProvider.
     * 
     * Initializes the random number generator and sets up internal structures.
     * Outputs a log message upon construction.
     */
    RandomProvider() {std::cout << "RandomProvider constructor" << std::endl;}  
    // random number generator
#ifdef USE_CPP17_FEATURES
    static inline thread_local std::mt19937 _default_rng{getSeed()};
#else
    static std::mt19937 _default_rng;
#endif
    std::map<int, std::pair<std::mt19937, std::mutex>>_rng_map;
    mutable  std::mutex _rng_map_mutex;

    /**
     * @brief Asserts a compile-time error for unsupported types.
     * 
     * This template struct is used to generate a compile-time error if an attempt 
     * is made to generate a random value for a type that does not have a specialized 
     * implementation.
     * 
     * @tparam T The type for which random generation is attempted.
     */
    template <typename T> 
    struct always_false : std::false_type {}; 

    /**
     * @brief Generates a random value for specialized types.
     * 
     * This template method is specialized for certain types to provide random value 
     * generation. It should be specialized for each supported type.
     * 
     * @tparam T The type for which to generate a random value.
     * @param min_len The minimum length or value (for strings and other types).
     * @param max_len The maximum length or value (for strings and other types).
     * @param rng The random number generator to use.
     * @return A random value of type T.
     */
    template <typename T>
    T getRandomSpecialized(int min_len, int max_len, std::mt19937& rng = getDefaultRNG()) {
        static_assert(always_false<T>::value, "No specialization for this type!");
    }
    
public:
    /**
     * @brief Retrieves the singleton instance of RandomProvider.
     * 
     * This method provides global access to the single RandomProvider instance, 
     * ensuring that random number generation is centralized and consistent.
     * 
     * @return Reference to the singleton RandomProvider instance.
     */
    static RandomProvider& getInstance() {
        static RandomProvider Instance;
        return Instance;
    }
    // Deleted copy and move constructors and assignment operators
    RandomProvider(const RandomProvider&) = delete;
    RandomProvider& operator=(const RandomProvider&) = delete;

    RandomProvider(const RandomProvider&&) = delete;
    RandomProvider& operator=(const RandomProvider&&) = delete;
/************************** Pseudorandom generator  **************************/
    /**
     * @brief Generates a default random value of type T within the specified range.
     * 
     * @tparam T The type of value to generate (e.g., int, double, std::string).
     * @param range The range within which the random value should be generated.
     * @return A random value of type T.
     */
    template <typename T>
    inline T getDefaultRandom(Range<T> range = {}) noexcept{
        return getRandomValue<T>(range, _default_rng);
    }
    /**
     * @brief Generates a random value of type T using a specific seed.
     * 
     * @tparam T The type of value to generate.
     * @param id The identifier for the specific random number generator seed.
     * @param range The range within which the random value should be generated.
     * @param errorHandler An optional error handler function to call if an exception occurs.
     * @return A random value of type T.
     * @throws Any exception that occurs during random number generation if no error handler is provided.
     */
    template <typename T>
    inline T getRandomFromSeed(int id, Range<T> range = {}, std::function<T()> errorHandler = nullptr) {
        try {
            std::lock_guard<std::mutex> lock(_rng_map[id].second);
            return getRandomValue<T>(range, _rng_map[id].first);
        } catch (...) {
            if (errorHandler) {
                return errorHandler();
            }
            throw;  // if no error handler, throw exception again
        }
    }
    /**
     * @brief Returns a reference to the default random number generator.
     * 
     * @return Reference to the default std::mt19937 random number generator.
     */
    static inline std::mt19937& getDefaultRNG() noexcept {
        return RandomProvider::_default_rng;
    }
    /**
     * @brief Sets the seed for the default random number generator.
     * 
     * @param seed The seed value to set. 
     */
    inline void setSeedDefaultRNG(unsigned int seed = getSeed()) noexcept {
        RandomProvider::_default_rng.seed(seed);
    }
    /**
     * @brief Adds a new random number generator with a specific seed.
     * 
     * @param id The identifier for the new random number generator.
     * @param seed The seed value for the new random number generator.
     */
    inline void addRNGWithSeed(int id, unsigned int seed = getSeed()) noexcept{
        std::lock_guard<std::mutex> lock(_rng_map_mutex);
        _rng_map[id].first = std::mt19937(seed);
    }
    /**
     * @brief Checks if a random number generator with a specific seed exists.
     * 
     * @param id The identifier of the random number generator to check.
     * @return True if the random number generator exists, false otherwise.
     */
    inline bool checkSeedExist(int id) const noexcept{
        std::lock_guard<std::mutex> lock(_rng_map_mutex);
        return _rng_map.find(id) != _rng_map.end();
    }
    /**
     * @brief Removes a random number generator with a specific seed.
     * 
     * @param id The identifier of the random number generator to remove.
     */
    inline void removeRNG(int id) noexcept{
        std::lock_guard<std::mutex> lock(_rng_map_mutex);
        _rng_map.erase(id);
    }
    /**
     * @brief Cleans up all custom random number generators, leaving only the default generator.
     */
    inline void cleanAllRNG() noexcept{
        std::lock_guard<std::mutex> lock(_rng_map_mutex);
        _rng_map.clear();
    }
#ifdef USE_CPP17_FEATURES
    /**
     * @brief Generates a random value of type T within the specified range using C++17 features.
     * 
     * This template method generates a random value for the given type T. It supports
     * basic data types like bool, integral types, floating-point types, and std::string.
     * 
     * @tparam T The type of value to generate.
     * @param range The range object defining the min and max values or other properties for generation.
     * @param rng The random number generator to use.
     * @return A random value of type T.
     */
    template <typename T>
    T getRandomValue(Range<T> range = {}, std::mt19937& rng = getDefaultRNG()) {
        if constexpr(std::is_same_v<T,bool>){
            std::bernoulli_distribution distr(range.probability_of_true);
            return distr(rng);
        }else if constexpr (std::is_integral_v<T>){
            std::uniform_int_distribution<T> distr(range.min_val, range.max_val);
            return distr(rng);
        }else if constexpr (std::is_floating_point_v<T>){
            std::uniform_real_distribution<T> distr(range.min_val, range.max_val);
            return distr(rng);
        }else if constexpr (std::is_same_v<T, std::string>){
            return getRandomSpecialized<std::string>(range.min_len, range.max_len);
        }else{
            return getRandomSpecialized<T>();
        }
    }
#else
    /**
     * @brief Generates a random string of a specified length.
     * 
     * This method generates a random string whose length is within the specified range. Each character 
     * in the string is chosen randomly from the uppercase English alphabets.
     * 
     * @param min_len The minimum length of the generated string.
     * @param max_len The maximum length of the generated string.
     * @param rng The random number generator to use.
     * @return A random string of length within the specified range.
     */
    template <typename T>
    T getRandomValue(Range<T> range, std::mt19937& rng);
#endif

};



template <>
inline std::string RandomProvider::getRandomSpecialized<std::string>(int min_len, int max_len, std::mt19937& rng) {
     /**
     * @brief Generates a random string of a specified length.
     * 
     * This method generates a random string whose length is within the specified range. Each character 
     * in the string is chosen randomly from the uppercase English alphabets.
     * 
     * @param min_len The minimum length of the generated string.
     * @param max_len The maximum length of the generated string.
     * @param rng The random number generator to use.
     * @return A random string of length within the specified range.
     */
    std::uniform_int_distribution<> distr(0, 25);

    std::uniform_int_distribution<> lenDistr(min_len, max_len);
    int length = lenDistr(rng);
    std::string result;
    for (int i = 0; i < length; ++i) {
        result.push_back('A' + distr(rng));
    }
    return result;
}
#ifndef USE_CPP17_FEATURES
/**
 * @brief Generates a random value of type T within the specified range for pre-C++17 environments.
 * 
 * This template method generates a random value for the given type T. It supports
 * basic data types like bool, integral types, floating-point types, and std::string.
 * The method uses type traits to determine the appropriate distribution for each type.
 * 
 * @tparam T The type of value to generate.
 * @param range The range object defining the min and max values or other properties for generation.
 * @param rng The random number generator to use.
 * @return A random value of type T.
 * @throws std::runtime_error if T is a type that does not have a specialized implementation.
 */
template <typename T>
template <typename T>
T RandomProvider::getRandomValue(Range<T> range, std::mt19937& rng) {
    if (std::is_integral<T>::value) {
        // Generate a random integer within the specified range
        std::uniform_int_distribution<T> distr(range.min_val, range.max_val);
        return distr(rng);
    } else if (std::is_floating_point<T>::value) {
        // Generate a random floating-point number within the specified range
        std::uniform_real_distribution<T> distr(range.min_val, range.max_val);
        return distr(rng);
    } else if (std::is_same<T, bool>::value) {
        // Generate a random boolean value based on the specified probability
        std::bernoulli_distribution distr(range.probability_of_true);
        return distr(rng);
    } else if (std::is_same<T, std::string>::value) {
        // Generate a random string with a length within the specified range
        return getRandomSpecialized<std::string>(range.min_len, range.max_len);
    } else {
        // For other types, call the specialized function
        return getRandomSpecialized<T>();
    }
}
#endif